/*
 * Copyright 2006-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.martinimix.webflow.action;

import org.springframework.validation.Errors;
import org.springframework.webflow.action.FormAction;
import org.springframework.webflow.action.FormObjectAccessor;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

/**
 * Provides a flow execution exception aware form action implementation.
 * 
 * <p>
 * Spring Web Flow allows flow builders to execute transitions on exception
 * conditions but does not provide an entry point to allow those exceptions
 * to be mapped to messages or errors.
 * </p>
 * 
 * <p>
 * This class extends <code>FormAction</code> to add such support.
 * </p>
 * 
 * @author Scott Rossillo
 *
 */
public abstract class AbstractExceptionAwareFormAction extends FormAction {
	
	/**
	 * Creates a new exception aware form action.
	 */
	public AbstractExceptionAwareFormAction() { }
	
	/**
	 * Spring Web Flow maps thrown flow execution exceptions to a key
	 * named <code>rootCauseException</code> in the FLASH scope.
	 */
	private static final String EXCEPTION_KEY = "rootCauseException";
	
	/**
	 * The flash scoped key signaling that the exception state has already
	 * been handled.  Necessary for page refreshes.
	 */
	private static final String EXCEPTION_HANDLED_KEY = "net.martinimix.webflow.action.exceptionHandled";
	
	
	/**
	 * Maps the given exception to a form error in the given errors
	 * object.  This method allows custom error handling to be executed
	 * during form setup via {@link #setupForm(RequestContext)}.
	 * 
	 * @param ex
	 * @param errors
	 */
	public abstract void onException(Exception ex, Errors errors);


	/**
	 * Delegates form setup to {@link #setupFormInternal(RequestContext)}.
	 * Additionally, if an exception is present in the flash scope, {@link #onException(Exception, Errors)}
	 * is called to map to the thrown exception into the form's <code>Errors</code>. 
	 * 
	 * @see org.springframework.webflow.action.FormAction#setupForm(org.springframework.webflow.execution.RequestContext)
	 * @see #setupFormInternal(RequestContext)
	 * 
	 * @throws Exception if an error occurs while setting up the form
	 */
	public final Event setupForm(RequestContext context) throws Exception {

		super.setupForm(context);
		FormObjectAccessor accessor = new FormObjectAccessor(context);
		Errors errors = accessor.getCurrentFormErrors();
		Exception ex = (Exception) context.getFlashScope().get(EXCEPTION_KEY);
		
		boolean exceptionHandled = 
			context.getFlashScope().getBoolean(EXCEPTION_HANDLED_KEY, Boolean.FALSE).booleanValue();
		
		if(!exceptionHandled && ex != null) {
			this.onException(ex, errors);
			context.getFlashScope().put(EXCEPTION_HANDLED_KEY, Boolean.TRUE);
		}
		
		return this.setupFormInternal(context);
	}
	
	/**
	 * Prepares a form object for display in a new form.
	 * 
	 * @param context the action execution context, for accessing and setting data in "flow scope" or "request scope"
	 * 
	 * @return a success <code>Event</code>
	 * 
	 * @throws Exception  an unrecoverable exception occurs, either checked or unchecked
	 * 
	 */
	public Event setupFormInternal(RequestContext context) throws Exception {
		
		return success();
	}

}
